package com.mikesamuel.dateseries.shared;

public final class DateUtil {
  private static final int[] MONTH_LENGTHS = {
    0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  };

  public static boolean isLeapYear(int year) {
    return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
  }


  public static int daysInMonth(int year, int month) {
    return month == 2 ? (isLeapYear(year) ? 29 : 28) : MONTH_LENGTHS[month];
  }

  /**
   * Convert a date value into a number of days since epoch,
   * which is the imaginary beginning of year zero in a hypothetical
   * backward extension of the Gregorian calendar through time.
   * See Calendrical Calculations, Reingold and Dershowitz.
   */
  public static int fixedFromGregorian(int year, int month, int day) {
    int yearM1 = year - 1;
    return 365 * yearM1 +
      ((yearM1 / 4) | 0) -
      ((yearM1 / 100) | 0) +
      ((yearM1 / 400) | 0) +
      (((367 * month - 362) / 12) | 0) +
      (month <= 2 ? 0 : (isLeapYear(year) ? -1 : -2)) +
      day;
  }

  public static int fixedFromGregorian(Day d) {
    return fixedFromGregorian(d.year(), d.month(), d.day());
  }

  public static int daysBetween(Day a, Day b) {
    if (a.year() == b.year() && a.month() == b.month()) {
      return a.day() - b.day();
    }
    return fixedFromGregorian(a) - fixedFromGregorian(b);
  }

  public static Day startOfWeek(Day d, WeekDay wkst) {
    return d.withDay(d.day() - ((7 + d.dayOfWeek().minus(wkst)) % 7));
  }

  /**
   * Given a date, returns the date that is at most n periods of length freq
   * forward of it and starts a period of size freq.
   */
  public static Day addPeriod(Day d, Freq f, int n, WeekDay wkst) {
    int year = d.year();
    int month = d.month();
    int day = d.day();
    switch (f) {
      case DAILY:
        day += n;
        break;
      case WEEKLY:
        day -= (7 + d.dayOfWeek().minus(wkst)) % 7;  // Shift to start of week.
        day += 7 * n;
        break;
      case MONTHLY:
        day = 1;  // Shift to start of month.
        month += n;
        break;
      case YEARLY:
        day = month = 1;  // Shift to start of year.
        year += n;
        break;
    }
    Day out = new Day(year, month, day);
    int delta = out.compareTo(d);
    if (delta != 0 && (delta > 0) != (n > 0)) {
      throw new IllegalStateException(
        "Over or underflow, probably of the year field. " + d + " -> " + out);
    }
    return out;
  }

}
